package com.video.commons.sendaop;

import com.qf.entity.ResultData;
import com.video.commons.loginaop.LoginContact;
import com.video.entity.Webcon;
import com.video.netty.commons.utils.ChannelUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpSession;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;

@Component
@Aspect
public class SocketAop implements ApplicationContextAware {

    @Autowired
    private ChannelUtil channelUtil;

    @Autowired
    private ApplicationContext applicationContext;

    private ExpressionParser parser = new SpelExpressionParser();

    private LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();

    @Around("@annotation(SocketSend)")
    public Object sendSocket(ProceedingJoinPoint joinPoint) throws Throwable {

        //获得目标方法的注解
        Method method = getMethod(joinPoint);

        //获得方法上的注解
        SocketSend annotation = method.getAnnotation(SocketSend.class);

        //判断是否在之后执行业务
        Object result = null;
        if (annotation.isAfter()) {
            //如果AOP业务在之后执行，就想调用目标方法
            result = joinPoint.proceed();

            if (result instanceof ResultData) {
                ResultData resultData = (ResultData) result;
                //如果状态是失败的，就不再通过aop发送消息给golang了
                if (resultData.getCode() == ResultData.ResultCodeList.ERROR) {
                    return result;
                }
            }
        }


        //获得注解上的参数
        String[] params = annotation.params();
        List<Object> paramsList = new ArrayList<>();
        if(params.length > 0){
            for (String param : params){
                Object value = null;
                if(param.equals("#result")){
                    value = result;
                } else {
                    value = parseSpel(method, joinPoint.getArgs(), param);
                }

                if(value != null){
                    paramsList.add(value);
                }
            }
        }

        //获得实际处理的类
        Class<? extends ISocketSend> socketClass = annotation.sendClass();
//        ISocketSend iSocketSend = socketClass.newInstance();
        ISocketSend iSocketSend = applicationContext.getBean(socketClass);
        //如果容器中没有该Bean
        if(iSocketSend != null){

            //调用实际目标类处理数据
            Map<String, Object> map = iSocketSend.sendMsg(paramsList.toArray());

            //获得处理的结果，并且封装信息
            //生成当前消息id
            String msgid = UUID.randomUUID().toString();

            if(map != null){
                //获得登录的用户信息
                HttpSession session = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest().getSession();
                Webcon webcon = (Webcon) session.getAttribute(LoginContact.WEBCON_USER.getKey());
                if(webcon != null){
                    map.put("account", webcon.getAccount());
                    map.put("taskPriority", webcon.getPriority());
                }
                map.put("msgid", msgid);

                //发送消息之前执行
                iSocketSend.preMsg(msgid);

                //发送json
                channelUtil.sendMsg(map);

                //发送完处理后续结果
                result = iSocketSend.postMsg(msgid, result);
            }

        }

        //如果aop业务不是在目标之后执行
        if (!annotation.isAfter() && result == null){
            //执行目标业务
            //如果AOP业务在之后执行，就想调用目标方法
            result = joinPoint.proceed();
        }


        //处理结果
        return result;
    }

    /**
     * 获取目标的方法
     * @param joinPoint
     * @return
     */
    private Method getMethod(ProceedingJoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        if (method.getDeclaringClass().isInterface()) {
            try {
                method = joinPoint
                        .getTarget()
                        .getClass()
                        .getDeclaredMethod(joinPoint.getSignature().getName(),
                                method.getParameterTypes());
            } catch (SecurityException | NoSuchMethodException e) {
                throw new RuntimeException(e);
            }
        }
        return method;
    }

    /**
     * 解析 spel 表达式
     *
     * @param method    方法
     * @param arguments 参数
     * @param spel      表达式
     * @return 执行spel表达式后的结果
     */
    private Object parseSpel(Method method, Object[] arguments, String spel) {
        String[] params = discoverer.getParameterNames(method);
        EvaluationContext context = new StandardEvaluationContext();
        for (int len = 0; len < params.length; len++) {
            context.setVariable(params[len], arguments[len]);
        }
        try {
            Expression expression = parser.parseExpression(spel);
            return expression.getValue(context);
        } catch (Exception e) {
            return null;
        }
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}
